﻿//-----------------------------------------------------------------------
// <copyright file="BaseXmlFileTask.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation.  All rights reserved.
// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY 
// KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
// </copyright>
// <author>Steve Overall</author>
// <email>v-stevov</email>
// <date>2005-04-15</date>
// <summary>Abstract base type for the various XmlFile tasks.</summary>
//-----------------------------------------------------------------------

namespace Microsoft.Sdc.Tasks.XmlFile
{
	#region Using directives

	using System;
	using System.IO;
	using System.Xml;
	using System.Text;
	using System.Reflection;
	using System.Collections.Generic;

	using Microsoft.Build.Tasks;
	using Microsoft.Build.Framework;
	using Microsoft.Build.Utilities;

	using Microsoft.Sdc.Tasks;

	#endregion

	/// <summary>
	/// Abstract base type for the various XmlFile tasks.
	/// </summary>
	public abstract class BaseXmlFileTask : TaskBase
	{
		#region private declarations
		private string		_path				= String.Empty;
		private string		_xpath				= String.Empty;
		private ITaskItem[] _namespaces			= null;
		#endregion

		#region constructors
		/// <summary>
		/// Create a new BaseXmlFileTask.
		/// </summary>
		protected BaseXmlFileTask() : base()
		{
		}
		#endregion

		#region public Properties
		/// <summary>
		/// The XPath Expression to run against the document to find the elements to change the value of.
		/// </summary>
		/// <value>The XPath Expression to run against the document to find the elements to change the value of.</value>
		[Required]
		public string XPath
		{
			get { return _xpath; }
			set { _xpath = (value != null ? value : String.Empty); }
		}

		/// <summary>
		/// The full path and filename of the file to update.
		/// </summary>
		/// <value>The full path and filename of the file to update.</value>
		[Required]
		public string Path
		{
			get { return _path; }
			set { _path = (value != null ? value : String.Empty); }
		}

		/// <summary>
		/// An optional array of TaskItems specifiying "Prefix" and "Uri" attributes for use with the specified <i>XPath</i>.
		/// </summary>
		/// <value>An array of TaskItems specifiying "Prefix" and "Uri" attributes for use with the specified <i>XPath</i>.</value>
		public ITaskItem[] Namespaces
		{
			get { return _namespaces; }
			set { _namespaces = value; }
		}
		#endregion

		#region protected Properties
		/// <summary>
		/// Publishes just the filename from the assigned Path.
		/// </summary>
		/// <value>Publishes just the filename from the assigned Path.</value>
		protected string FileName
		{
			get
			{
				if (_path.Length > 0)
					return System.IO.Path.GetFileName( _path );
				else
					return String.Empty;
			}
		}
		#endregion

		#region protected methods
		#region SelectXmlNodesOfType overloads
		/// <summary>
		/// Will return an XmlNodeList containing those nodes within the document
		/// that match the passed XPath expression.  Will also check that all of the
		/// nodes matching the XPath also match the requested nodeType.
		/// </summary>
		/// <param name="doc">Source document for the xPath.</param>
		/// <param name="xpathExpression">XPath query to use to select nodes from the document.</param>
		/// <param name="nodeType">The type of Node we are expecting all the results to match.</param>
		/// <returns>List of the XmlNodes matching the XPath.</returns>
		protected XmlNodeList SelectXmlNodesOfType( XmlNode doc, string xpathExpression, XmlNodeType nodeType )
		{
			if (doc == null) throw new System.ArgumentNullException( "doc" );
			if (xpathExpression == null) throw new System.ArgumentNullException( "xpathExpression" );

			//	Use the xpath expression to select the nodes.
			XmlNodeList nodes = doc.SelectNodes( xpathExpression );

			if (nodes != null && nodes.Count > 0)
			{
				//	Check each node is of the requested type.
				foreach (XmlNode node in nodes)
				{
					if (node.NodeType != nodeType)
						throw new Exception( "Invalid node type." );
				}
			}

			return nodes;
		}

		/// <summary>
		/// Will return an XmlNodeList containing those nodes within the document
		/// that match the passed XPath expression.  Will also check that all of the
		/// nodes matching the XPath also match the requested nodeType.
		/// </summary>
		/// <param name="doc">Source document for the xPath.</param>
		/// <param name="xpathExpression">XPath query to use to select nodes from the document.</param>
		/// <param name="nodeType">The type of Node we are expecting all the results to match.</param>
		/// <param name="nsMgr">Namespace manager for the document.</param>
		/// <returns>List of the XmlNodes matching the XPath.</returns>
		protected XmlNodeList SelectXmlNodesOfType( XmlNode doc, string xpathExpression, XmlNodeType nodeType, XmlNamespaceManager nsMgr )
		{
			if (doc == null) throw new System.ArgumentNullException( "doc" );
			if (xpathExpression == null) throw new System.ArgumentNullException( "xpathExpression" );

			//	Use the xpath expression to select the nodes.
			XmlNodeList nodes = doc.SelectNodes( xpathExpression, nsMgr );

			if (nodes != null && nodes.Count > 0)
			{
				//	Check each node is of the requested type.
				foreach (XmlNode node in nodes)
				{
					if (node.NodeType != nodeType)
						throw new TaskException( Log.FormatResourceString( "XmlFile.UnsupportedNodeType.Exception", node.NodeType ) );
				}
			}

			return nodes;
		}
		#endregion

		/// <summary>
		/// Opens the config file specified by the passed path and returns it as an XmlDocument.
		/// </summary>
		/// <param name="path">Full Path + Filename of the XML file to open.</param>
		/// <returns>The requested config file.</returns>
		protected XmlDocument OpenXmlFile( string path )
		{
			if (!System.IO.File.Exists( path ))
			{
				throw new TaskException( Log.FormatResourceString( "FileNotFound.Exception", path ) );
			}

			XmlDocument xmlDoc = new XmlDocument();
			xmlDoc.Load( path );

			return xmlDoc;
		}

		/// <summary>
		/// Save the passed XML Document to disc, with the passed path.
		/// </summary>
		/// <param name="xmlDoc">XmlDocument to save.</param>
		/// <param name="path">The file name to save it as.</param>
		/// <param name="forceSave">Force overwrite of ReadOnly file.</param>
		protected void SaveXmlFile( XmlDocument xmlDoc, string path, bool forceSave )
		{
			if (forceSave)
			{
				FileInfo fi = new FileInfo( path );
				fi.Attributes = FileAttributes.Normal;
			}
			xmlDoc.Save( path );
		}

		/// <summary>
		/// This will get a populated NamespaceManager for the passed document.
		/// It will be populated with any declarations read from the passed
		/// namespaces collection.
		/// </summary>
		/// <param name="xmlDoc">The document to create the NamespaceManager for.</param>
		/// <param name="namespaces">An array of ITaskItem types representing each of the namespaces to use in this document.</param>
		/// <returns>A namespace manager for the document, populated with the namespaces passed in.</returns>
		protected XmlNamespaceManager GetNamespaceManagerForDoc( XmlDocument xmlDoc, ITaskItem[] namespaces )
		{
			if (xmlDoc == null) throw new ArgumentNullException( "xmlDoc" );

			XmlNamespaceManager nsMgr = new XmlNamespaceManager( xmlDoc.NameTable );

			//	If we have had namespace declarations specified add them to the Namespace Mgr for the XML Document.
			if (namespaces != null && namespaces.Length > 0)
			{
				foreach (ITaskItem item in namespaces)
				{
					string prefix = item.GetMetadata( "Prefix" );
					string uri = item.GetMetadata( "Uri" );

					nsMgr.AddNamespace( prefix, uri );
				}
			}

			return nsMgr;
		}

		/// <summary>
		/// This will return the first child element of the passed parent node
		/// found that matches the childName, keyAttribName and keyAttribValue criteria.
		/// If keyAttribName and keyAttribValue are provided then we look for a child
		/// element that has the requested name and has an attribute/value combination
		/// matching keyAttribName/keyAttribValue. 
		/// </summary>
		/// <param name="parent">Parent node to find the child within.</param>
		/// <param name="nsMgr">Namespace manager to use with XPath.</param>
		/// <param name="childName">Name of the child element to look for.</param>
		/// <param name="keyAttribName">Key Attribute name to look for.</param>
		/// <param name="keyAttribValue">Key Attribute value to look for.</param>
		/// <returns></returns>
		protected XmlNode GetChildElement( XmlNode parent, XmlNamespaceManager nsMgr, string childName, string keyAttribName, string keyAttribValue )
		{
			if (parent == null) throw new ArgumentNullException( "parent" );
			if (parent.NodeType != XmlNodeType.Element) throw new ArgumentException( "Must be of NodeType XmlNodeType.Element", "parent" );

			string xpath = "";

			if (keyAttribName.Length > 0)
			{
				//	We have a Key/Value pair.  Look for a child element with the passed name + Key/Value pair.
				xpath = string.Format( "{0}[{1}='{2}']", childName, keyAttribName, keyAttribValue );
			}
			else
			{
				//	No key value pair.  Just look for a child element with the passed name.
				xpath = childName;
			}

			return parent.SelectSingleNode( xpath, nsMgr );
		}
		#endregion
	}
}
